/*     kmnl		11.1.2011
*
* a kernel module driver simulating (along with daemon) a camera (in V4L2 standart)
*
* base on code from Tomer Margalit's netlinks example and The Linux Kernel Module Programming Guide (Copyright © 2001)
* written by Peter Jay Salzman
*
*/

#include <linux/module.h>	//Standard include for modules.
#include <linux/init.h>		//Included so that we can use custom init and exit functions.
#include <linux/kernel.h>	//KERN_* macros.
#include <linux/fs.h>
#include <asm/uaccess.h>	/* for get_user and put_user */
#include "../common/common.h"
#include <net/netlink.h>
#include <net/genetlink.h>
#include <net/net_namespace.h>	//needed this to make tomer's program work
#include "../camdev/camdev.h"
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/mutex.h>
#include <linux/videodev.h>


#define DEVICE_NAME "cam_dev"
#define BUF_LEN 80
#define SUCCESS 0
//GLOBALS

//global flags;
static int daemonCon;
static int userCon;
static int Quit;

//global vars. to work with frames
static char frame_buffer[MSG_BUFFER_SIZE];
static char *frame_buffer_ptr;
static int status;
static int chunkSize;
static int format;
static int height;
static int width;

//global lockings
struct mutex mutex;
wait_queue_head_t wqUser;
wait_queue_head_t wqDaemon;
wait_queue_head_t wqQuit;


int bin2ctx(void *buffer){
	int *ptr = (int *)buffer;

	if (buffer == NULL) return 0;

	memcpy(&width, ptr, sizeof(int));

	ptr++;
	memcpy(&height, ptr, sizeof(int));

	ptr++;
	memcpy(&format, ptr, sizeof(int));

	return 1;
}

/* family definition */
static struct genl_family ex_genl_family = {
      .id = GENL_ID_GENERATE,
      .hdrsize = FAMILY_HEADER_LEN,
      .name = FAMILY_NAME,
      .version = 1,
      .maxattr = MAX_ATTR_INDEX ,
};

//Policy for FRAME_ATTR.
static struct nla_policy ex_policy[MAX_ATTR_INDEX + 1] = \
	{ [FRAME_ATTR] = { .type = NLA_BINARY }, 
	[FIN_ATTR] = { .type = NLA_BINARY },
	[CTX_ATTR] = { .type = NLA_BINARY }, 
	[EO_FRAME_ATTR] = { .type = NLA_BINARY },
	[REQ_FRAME_ATTR] = { .type = NLA_BINARY },
	[OK_ATTR] = { .type = NLA_BINARY },
	[START_ATTR] = { .type = NLA_BINARY },
	[QUIT_ATTR] = { .type = NLA_BINARY }};

static struct genl_multicast_group ex_mc_grp = { .name = MCG_NAME };

//Callback for inbound messages.
static int msg_cb(struct sk_buff *skb, struct genl_info *info);

/* operation definition */
static struct genl_ops ex_gops = {
      .cmd = STD_CMD,
      .flags = 0,
      .policy = ex_policy,
      .doit = msg_cb,
      .dumpit = NULL,
};

static int send_msg(void *buff, unsigned int len, int attr, struct net *n);

static inline int send_std_msg(void *buff, unsigned int len, struct net *n)
{
	return send_msg(buff, len, FRAME_ATTR, n);
}

static inline int send_frame_msg(void *buff, unsigned int len, struct net *n)
{
	return send_msg(buff, len, FRAME_ATTR, n);
}

static inline int send_eo_frame_msg(void *buff, unsigned int len, struct net *n)
{
	return send_msg(buff, len, EO_FRAME_ATTR, n);
}


static inline int send_fin_msg(struct net *n)
{
	return send_msg("ECHO", strlen("ECHO"), FIN_ATTR, n);
}

static inline int send_req_msg(struct net *n)
{
	return send_msg("REQ", strlen("REQ"), REQ_FRAME_ATTR, n);
}

static inline int send_ok_msg(struct net *n){
	return send_msg("OK", strlen("OK"), OK_ATTR, n);
}

static inline int send_quit_msg(struct net *n){
	return send_msg("quit", strlen("quit"), QUIT_ATTR, n);
}


static int send_msg(void *buff, unsigned int len, int attr, struct net *n)
{
	struct sk_buff *skb;
	void *msg_head;
	int rc = -1;

	skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
	if (skb == NULL)
	{
		rc = -ENOMEM;
		goto msg_err;
	}
		
	/* Create the message headers */
	msg_head = genlmsg_put(skb, 1000, 0, &ex_genl_family, 
			NLM_F_MULTI, 
			0);

	if (msg_head == NULL) {
		rc = -ENOMEM;
		goto msg_err;
	}

	/* Add an attribute */
	rc = nla_put(skb, attr, len, buff);
	if (rc != 0)
		goto msg_err;

	/* Finalize the message (Cannot fail).*/
	genlmsg_end(skb, msg_head);

	rc = genlmsg_unicast(n, (struct sk_buff *)skb, (u32)1000);
	goto end;

msg_err:
	if (skb == NULL)
		goto end;

	if (msg_head != NULL)
		genlmsg_cancel(skb, msg_head);
	
	nlmsg_free(skb);

end:	
	return rc;

}

/* handler */
static int msg_cb(struct sk_buff *skb, struct genl_info *info)
{
	int len = nlmsg_len(info->nlhdr);
	void *data = nlmsg_data(info->nlhdr);
	char *buff = NULL;
	int ret = 0;

	struct nlattr *attr1 = NULL;

	if (len < 0)
	{
		return -1;
	}

	if (data == NULL)
	{
		return -1;
	}

	if (Quit && !(info->attrs[FIN_ATTR])){
		ret = send_quit_msg(info->_net);
		status = 0;
		wake_up(&wqUser);
		return 0;
	}

	
	if (info->attrs[FRAME_ATTR] || info->attrs[EO_FRAME_ATTR]) 
	{
		wait_event(wqDaemon, ((status == 1) || (Quit == 1)));
		if (Quit){
			ret = send_quit_msg(info->_net);
			status = 0;
			wake_up(&wqUser);
			return 0;
		}

		
		if (info->attrs[FRAME_ATTR]){
			attr1 = info->attrs[FRAME_ATTR];
		}
		else {
			attr1 = info->attrs[EO_FRAME_ATTR];
		}
			
		buff = nla_data(attr1);
		chunkSize = nla_len(attr1);
		frame_buffer_ptr = frame_buffer;
		memcpy(frame_buffer_ptr, buff, nla_len(attr1));


		if (info->attrs[FRAME_ATTR]){
			ret = send_ok_msg(info->_net);
			if (ret != 0){
				return -1;
			}	
		}
		else {
			ret = send_fin_msg(info->_net);
			if (ret != 0){
				return -1;
			}
		}
		status = 0;
		wake_up(&wqUser);

		return 0;
	}
	else if (info->attrs[START_ATTR]){
		mutex_lock(&mutex);
		daemonCon = 1;
		mutex_unlock(&mutex);
		status = 0;
		frame_buffer_ptr = frame_buffer;
		ret = send_fin_msg(info->_net);
		if (ret != 0){
			return -1;
		}
		return 0;
	}
	else if (info->attrs[CTX_ATTR]){
		buff = nla_data(info->attrs[CTX_ATTR]);
		bin2ctx(buff);
		ret = send_ok_msg(info->_net);
		if (ret != 0){
			return -1;
		}	
		return 0;
	}
	else if (info->attrs[FIN_ATTR]){
		mutex_lock(&mutex);
		daemonCon = 0;
		Quit = 2;
		mutex_unlock(&mutex);
		wake_up(&wqUser);
		
		ret = send_ok_msg(info->_net);
		if (ret != 0){
			return -1;
		}	

		wake_up(&wqQuit);
		return 0;
	}

	return 0;
 }

int register_nlfamily(void)
{
	int rc;
	rc = genl_register_family(&ex_genl_family);
	if (rc != 0)
	{
		return -1;
	}


	rc = genl_register_ops(&ex_genl_family, &ex_gops);
	if (rc != 0)
	{
		return -1;
	}

	rc = genl_register_mc_group(&ex_genl_family, &ex_mc_grp);
	if (rc != 0)
	{
		return -1;
	}
	
	return 0;
}

int unregister_nlfamily(void)
{
	int rc = 0;

	rc = genl_unregister_family(&ex_genl_family);
	if (rc != 0)
	{
		return -1;
	}

	return 0;
}

static int device_open(struct inode *inode, struct file *file)
{
	mutex_lock(&mutex);
	userCon = 1;
	mutex_unlock(&mutex);

	try_module_get(THIS_MODULE);
	return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file)
{
	mutex_lock(&mutex);
	userCon = 0;
	mutex_unlock(&mutex);
	wake_up(&wqDaemon);
	

	module_put(THIS_MODULE);
	return SUCCESS;
}

static ssize_t device_read(struct file *file, char __user * buffer, size_t length, loff_t * offset)
{
	int bytes_read = 0;

	mutex_lock(&mutex);
	if (!daemonCon){
		mutex_unlock(&mutex);
		return 0;
	}
	mutex_unlock(&mutex);
	
	if (length > width*height*3){
		length = width*height*3;
	}

	while (length){
		
		status = 1;

		wake_up(&wqDaemon);

		if (Quit) return 0;

		wait_event(wqUser, (status == 0 || daemonCon == 0));
		
		for (frame_buffer_ptr = frame_buffer; length && chunkSize ; ){
			put_user(*(frame_buffer_ptr++), buffer++);
			chunkSize--;
			length--;
			bytes_read++;
		}
	
	}

	return bytes_read;
}

void querycap_function(unsigned long ioctl_param){
	put_user(V4L2_CAP_READWRITE, &((struct v4l2_capability *)ioctl_param)->capabilities);

	return;
}

void get_format_function(unsigned long ioctl_param){
	struct v4l2_format *f;

	f = (struct v4l2_format *)ioctl_param;

	put_user(width, &(f->fmt.pix.width));
	put_user(height, &(f->fmt.pix.height));
	put_user(V4L2_PIX_FMT_RGB24, &(f->fmt.pix.pixelformat));
	
	return;
}

int device_ioctl(struct inode *inode, struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
{
	/* 
	 * Switch according to the ioctl called 
	 */
	switch (ioctl_num) {
	case VIDIOC_QUERYCAP:
		querycap_function(ioctl_param);
		break;
	case VIDIOC_G_FMT:
		get_format_function(ioctl_param);
		break;
	}
	return SUCCESS;
}


struct file_operations Fops = {
	.read = device_read,
	.write = NULL,
	.ioctl = device_ioctl,
	.open = device_open,
	.release = device_release,
};

static int __init mod_ex_init(void)
{
	int ret_val;

	daemonCon = 0;
	userCon = 0;
		
	frame_buffer_ptr = frame_buffer;
	status = 0;
	width = 0;
	height = 0;
	format = 0;
	Quit = 0;

	mutex_init(&mutex);
	init_waitqueue_head(&wqDaemon);
	init_waitqueue_head(&wqUser);
	init_waitqueue_head(&wqQuit);

	if (register_nlfamily() < 0)
		return -1;

	ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

	if (ret_val < 0) {
		printk(KERN_ALERT "%s failed with %d\n",
		       "Sorry, registering the character device ", ret_val);
		return ret_val;
	}

	return 0;
}

static void __exit mod_ex_exit(void)
{
	Quit = 1;
	mutex_lock(&mutex);
	if (daemonCon){
		mutex_unlock(&mutex);
		wake_up(&wqDaemon);
		wait_event(wqQuit, (Quit == 2));
	}
	else {
		mutex_unlock(&mutex);
	}
	
	unregister_nlfamily();

	unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

	return;
}
MODULE_SUPPORTED_DEVICE("cam_dev");

MODULE_LICENSE("GPL");

module_init(mod_ex_init);
module_exit(mod_ex_exit);
